package sf.codegen;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.CtPrimitiveType;
import javassist.JavassistUtils;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sf.core.DBCascadeField;
import sf.core.DBField;
import sf.core.DBObject;
import sf.core.IEntity;
import sf.spring.util.Assert;
import sf.spring.util.StringUtils;
import sf.tools.ArrayUtils;
import sf.tools.SystemUtils;

import javax.persistence.Entity;
import javax.persistence.Table;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class EnhanceTaskJavassist {
    private Logger logger = LoggerFactory.getLogger(EnhanceTaskJavassist.class);
    /**
     * 关闭生成设置值.
     */
    public static boolean genPushPull = false;
    private static String dbObjectClzPath = DBObject.class.getName().replace(".", "/");
    private static String dbFieldPath = DBField.class.getName().replace(".", "/");

    public EnhanceTaskJavassist() {
    }

    /**
     * @param pool
     * @param cc
     * @return 返回null表示不需要增强，返回byte[0]表示该类已经增强，返回其他数据为增强后的class
     * @throws Exception
     */
    public byte[] doEnhance(ClassPool pool, CtClass cc) throws Exception {
        Assert.notNull(cc, "");
        try {
            return enhanceClass(pool, cc);
        } catch (EnhancedException e) {
            return ArrayUtils.EMPTY_BYTE_ARRAY;
        } finally {
            cc.detach();// 卸载
        }
    }

    /**
     * 执行增强方法,使用完成后不调用 cc.detach()方法
     * @param pool
     * @param cc   要修改的类
     * @return
     * @throws Exception
     */
    public byte[] enhanceClass(ClassPool pool, CtClass cc) throws Exception {
        byte[] b = null;

        if (cc.isInterface()) {
            return null;
        }

        if (!cc.hasAnnotation(Table.class) && !cc.hasAnnotation(Entity.class)) {
            return null;
        }

        boolean isEntityInterface = isEntityClass(cc);
        if (!isEntityInterface) {
            return null;
        }

        byte[] sfd = cc.getAttribute(IEntityEnhancer.ENHANCE_HEADER);

        if (sfd != null && sfd.length > 0) {
            throw new EnhancedException();
        } else {
            // 设置修改标志
            sfd = new byte[]{0x1f};
            ClassFile cf = cc.getClassFile();
            AttributeInfo ai = new AttributeInfo(cc.getClassFile().getConstPool(), IEntityEnhancer.ENHANCE_HEADER, sfd);
            cf.addAttribute(ai);

            // 查找Field定义的字段
            List<String> enumFieldList = new ArrayList<String>();
            List<String> enumCascadeFieldList = new ArrayList<>();
            CtClass dbFieldClass = null;
            String dbFieldClassName = "";
            CtClass[] innerCC = cc.getDeclaredClasses();
            if (innerCC != null && innerCC.length > 0) {
                for (int i = 0; i < innerCC.length; i++) {
                    CtClass ctClass = innerCC[i];
                    if (!ctClass.isEnum()) {
                        continue;
                    }
                    String[] interfeaces = ctClass.getClassFile2().getInterfaces();
                    String inter = null;
                    if (interfeaces != null && interfeaces.length > 0) {
                        inter = interfeaces[0];
                    }

                    if (DBField.class.getName().equals(inter)) {
                        CtField[] declaredFields = ctClass.getDeclaredFields();
                        for (int j = 0; j < declaredFields.length; j++) {
                            CtField ctf = declaredFields[j];
                            enumFieldList.add(ctf.getName());
                        }
                        dbFieldClass = ctClass;
                        dbFieldClassName = dbFieldClass.getName();
                        dbFieldClassName = dbFieldClassName.replace("$", ".");
                    } else if (DBCascadeField.class.getName().equals(inter)) {
                        CtField[] declaredFields = ctClass.getDeclaredFields();
                        for (int j = 0; j < declaredFields.length; j++) {
                            CtField ctf = declaredFields[j];
                            enumCascadeFieldList.add(ctf.getName());
                        }
                    }
                }
            }

            CtMethod[] ctms = cc.getDeclaredMethods();
            if (ctms != null && ctms.length > 0) {
                for (int i = 0; i < ctms.length; i++) {
                    CtMethod ctm = ctms[i];
                    String fieldName = "";
                    String fieldCapName = "";
                    if (!Modifier.isStatic(ctm.getModifiers())) {
                        if (ctm.getName().startsWith("set")) {
                            fieldCapName = ctm.getName().substring(3);
                            fieldName = StringUtils.uncapitalize(fieldCapName);
                            if (enumFieldList.contains(fieldName)) {
                                try {
                                    String wrapper = "($w)$1";
                                    CtClass innct = ctm.getParameterTypes()[0];
                                    String wrapperName = "";
                                    if (innct.isPrimitive()) {
                                        // 是否是原始类型
                                        CtPrimitiveType ctpt = (CtPrimitiveType) innct;
                                        wrapperName = ctpt.getWrapperName();
                                        wrapper = wrapperName + ".valueOf($1)";
                                    }

//                                String body = "{if (this._recordUpdate) { prepareUpdate(" + cc.getName() + ".Field."
//                                        + ctf.getName() + ", " + wrapper + "); } this." + ctf.getName() + "=$1;}";
                                    // ctm.setBody(body);

                                    String src = "if (this._recordUpdate) {prepareUpdate(" + dbFieldClassName + "."
                                            + fieldName + ",  " + wrapper + ");}";
                                    ctm.insertBefore(src);
                                } catch (Exception e) {
                                    setFieldBytecode(pool, dbFieldClass, fieldName, ctm);
                                }
                            } else if (enumCascadeFieldList.contains(fieldName)) {
                                try {
                                    String src = "beforeSet(\"" + fieldName + "\");";
                                    ctm.insertBefore(src);
                                } catch (Exception e) {
                                    setCascadeFieldBytecode(pool, fieldName, "beforeSet", ctm);
                                }
                            }
                        } else if (ctm.getName().startsWith("get")) {
                            fieldCapName = ctm.getName().substring(3);
                            fieldName = StringUtils.uncapitalize(fieldCapName);
                            if (enumCascadeFieldList.contains(fieldName)) {
                                try {
                                    String src = "beforeGet(\"" + fieldName + "\");";
                                    ctm.insertBefore(src);
                                } catch (Exception e) {
                                    setCascadeFieldBytecode(pool, fieldName, "beforeGet", ctm);
                                }
                            }
                        }
                    }
                }
            }

            if (genPushPull) {
                // 设置方法需要的参数(自动查找父类的)
                List<CtField> ctFields = new ArrayList<>();
                CtField[] cfs = null;
                CtClass superClass = cc;
                while (superClass != null && !DBObject.class.getName().equals(superClass.getName())) {
                    cfs = superClass.getDeclaredFields();
                    ctFields.addAll(Arrays.asList(cfs));
                    try {
                        superClass = superClass.getSuperclass();
                    } catch (NotFoundException e) {
                        break;
                    }
                }
                GenPushPull(cc, ctFields, ctms);
            }
            b = cc.toBytecode();
        }
        return b;
    }

    /**
     * 级联字段字节码修改方法
     * @param pool
     * @param fieldName
     * @param ctm
     * @throws BadBytecode
     */
    private void setCascadeFieldBytecode(ClassPool pool, String fieldName, String methodName, CtMethod ctm) {
        MethodInfo info = ctm.getMethodInfo2();
        Bytecode code = new Bytecode(info.getConstPool());
        code.addAload(0);
        code.addLdc(fieldName);
        code.addInvokevirtual(dbObjectClzPath, methodName, "(Ljava/lang/String;)V");
        CodeAttribute codeAttribute = info.getCodeAttribute();
        CodeIterator it = codeAttribute.iterator();
        try {
            int pos = it.insertEx(code.get());
            it.insert(code.getExceptionTable(), pos);
            codeAttribute.setMaxStack(codeAttribute.getMaxStack() + 2);
            info.rebuildStackMap(pool);
        } catch (BadBytecode badBytecode) {
            throw new RuntimeException(badBytecode);
        }
    }

    /**
     * 数据库字段字节码修改方法
     * @param pool
     * @param dbFieldEnumClass
     * @param fieldName
     * @param ctm
     * @throws BadBytecode
     */
    private void setFieldBytecode(ClassPool pool, CtClass dbFieldEnumClass, String fieldName, CtMethod ctm) {
        String path = dbFieldEnumClass.getName().replace('.', '/');
        MethodInfo info = ctm.getMethodInfo2();
        Bytecode code = new Bytecode(info.getConstPool());
        code.addAload(0);
        code.addGetfield(dbObjectClzPath, "_recordUpdate", "Z");
        int start = code.currentPc();
        code.addOpcode(Opcode.IFEQ);
        code.addIndex(0);//随机赋值
        code.addAload(0);
        code.addGetstatic(path, fieldName, "L" + path + ";");
        if (!JavassistUtils.setLoad(code, info.getDescriptor(), 1)) {
            code.addAload(1);
        }
        code.addInvokevirtual(dbObjectClzPath, "prepareUpdate", "(L" + dbFieldPath + ";Ljava/lang/Object;)V");

        int end = code.currentPc();
        code.write16bit(start + 1, end - start);//修改index的值

//      code.addAload(1);
//      code.addPutfield(path, fieldName, JavassitUtils.getDesc(ctf.getDeclaringClass().toClass()));
//      code.addReturn(CtClass.voidType);
        CodeAttribute codeAttribute = info.getCodeAttribute();
        CodeIterator it = codeAttribute.iterator();
        try {
            int pos = it.insertEx(code.get());
            it.insert(code.getExceptionTable(), pos);
            codeAttribute.setMaxStack(codeAttribute.getMaxStack() + 2);
            info.rebuildStackMap(pool);
        } catch (BadBytecode badBytecode) {
            throw new RuntimeException(badBytecode);
        }
    }

    /**
     * 生成push,pull方法
     * @param cc
     * @param ctFields
     * @param ctms
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private void GenPushPull(CtClass cc, List<CtField> ctFields, CtMethod[] ctms) throws
            CannotCompileException, NotFoundException {

        boolean existPullMethod = false;
        boolean existPushMethod = false;
        for (int i = 0; i < ctms.length; i++) {
            CtMethod method = ctms[i];
            if (method.getName().equals("pull")) {
                existPullMethod = true;
            }
            if (method.getName().equals("push")) {
                existPushMethod = true;
            }
        }
        if (!existPullMethod) {
            // 生成pull方法
            StringBuilder sb = new StringBuilder("public Object pull(String fieldName) {");
            sb.append(SystemUtils.lineSeparator).append(" switch (fieldName.hashCode()) {").append(SystemUtils.lineSeparator);
            for (CtField f : ctFields) {
                if (!Modifier.isFinal(f.getModifiers()) && !Modifier.isStatic(f.getModifiers())) {
                    int hashCode = f.getName().hashCode();
                    sb.append("case ").append(hashCode).append(": ").append(SystemUtils.lineSeparator).append(" ");

                    String wrapperName = "this." + f.getName();
                    if (f.getType().isPrimitive()) {
                        // 是否是原始类型
                        CtPrimitiveType ctpt = (CtPrimitiveType) f.getType();
                        wrapperName = ctpt.getWrapperName();
                        wrapperName = wrapperName + ".valueOf(this." + f.getName() + ")";
                    }

                    sb.append("if (fieldName.equals(\"").append(f.getName()).append("\")) {return ").append(wrapperName).append(";} break;")
                            .append(SystemUtils.lineSeparator);
                }
            }
            sb.append(" default: return super.pull(fieldName);}").append(SystemUtils.lineSeparator);
            sb.append("return null;}");
            CtMethod method = CtNewMethod.make(sb.toString(), cc);
            cc.addMethod(method);
        }
        if (!existPushMethod) {
            // 生成push方法
            StringBuilder sb = new StringBuilder(" public boolean push(String fieldName, Object value) {");
            sb.append(SystemUtils.lineSeparator).append(" switch (fieldName.hashCode()) {").append(SystemUtils.lineSeparator);
            for (CtField f : ctFields) {
                if (!Modifier.isFinal(f.getModifiers()) && !Modifier.isStatic(f.getModifiers())) {
                    int hashCode = f.getName().hashCode();
                    sb.append("case ").append(hashCode).append(": ").append(SystemUtils.lineSeparator).append(" ");
                    String wrapperName = "(" + f.getType().getName() + ")value";
                    if (f.getType().isPrimitive()) {
                        // 是否是原始类型
                        CtPrimitiveType ctpt = (CtPrimitiveType) f.getType();
                        wrapperName = ctpt.getWrapperName();
                        if (wrapperName.contains("Boolean")) {
                            wrapperName = "((Boolean)value)." + f.getType().getName() + "Value()";
                        } else if (wrapperName.contains("Character")) {
                            wrapperName = "((Character)value)." + f.getType().getName() + "Value()";
                        } else {
                            wrapperName = "((Number)value)." + f.getType().getName() + "Value()";
                        }
                    }
                    sb.append("if (fieldName.equals(\"").append(f.getName()).append("\")) {this.").append(f.getName()).append("=").append(wrapperName).append(";return true;} break;")
                            .append(SystemUtils.lineSeparator);
                }
            }
            sb.append(" default: return super.push(fieldName, value);}").append(SystemUtils.lineSeparator);
            sb.append("return false;}");
            CtMethod method = CtNewMethod.make(sb.toString(), cc);
            cc.addMethod(method);
        }
    }

    private boolean isEntityClass(CtClass clz) {
        CtClass[] interfaces = null;
        CtClass superClass = null;
        try {
            interfaces = clz.getInterfaces();
            superClass = clz.getSuperclass();
        } catch (NotFoundException e) {
            return false;
        }
        if (DBObject.class.getName().equals(superClass.getName())) {
            // 绝大多数实体都是继承这个类的
            return true;
        }
        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; i++) {
                CtClass ct = interfaces[i];
                if (IEntity.class.getName().equals(ct.getName())) {
                    return true;
                }
            }
        }

        if ("java.lang.Object".equals(superClass.getName())) {
            return false;
        }
        // 递归检查父类
        return isEntityClass(superClass);
    }

    private static ClassLoader getLocaleClassLoader(URL path) throws Exception {
        // 实例化类加载器
        return new URLClassLoader(new URL[]{path});
    }
}
